/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.web.gwt.basics.client.oracle;

import java.util.ArrayList;
import java.util.List;

import com.genia.toolbox.web.gwt.basics.client.support.GwtObjectUtils;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.KeyboardListenerAdapter;
import com.google.gwt.user.client.ui.SourcesChangeEvents;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestionEvent;
import com.google.gwt.user.client.ui.SuggestionHandler;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;

/**
 * A wrapper around a {@link SuggestBox} that force the user to choose a value
 * from the list of available values.
 */
public class SelectSuggestBox
    extends Composite
    implements SourcesChangeEvents
{

  /**
   * the event listener that reset the value of the box if the text has been
   * changed.
   */
  private class ResetSuggestBoxEventListener
      extends KeyboardListenerAdapter
      implements ChangeListener
  {

    /**
     * Fired when a widget changes, where 'change' is defined by the widget
     * sending the event.
     * 
     * @param sender
     *          the widget that has changed
     * @see com.google.gwt.user.client.ui.ChangeListener#onChange(com.google.gwt.user.client.ui.Widget)
     */
    public void onChange(Widget sender)
    {
      reinitValueIfNeeded();
    }



    /**
     * Fired when the user depresses a physical key.
     * 
     * @param sender
     *          the widget that was focused when the event occurred
     * @param keyCode
     *          the physical key that was depressed. Constants for this value
     *          are defined in this interface with the KEY prefix
     * @param modifiers
     *          the modifier keys pressed at when the event occurred. This value
     *          is a combination of the bits defined by
     *          {@link com.google.gwt.user.client.ui.KeyboardListener#MODIFIER_SHIFT},
     *          {@link com.google.gwt.user.client.ui.KeyboardListener#MODIFIER_CTRL},
     *          and
     *          {@link com.google.gwt.user.client.ui.KeyboardListener#MODIFIER_ALT}
     * @see com.google.gwt.user.client.ui.KeyboardListener#onKeyDown(Widget,
     *      char, int)
     */
    @Override
    public void onKeyDown(Widget sender, char keyCode, int modifiers)
    {
      reinitValueIfNeeded();
    }



    /**
     * Fired when the user releases a physical key.
     * 
     * @param sender
     *          the widget that was focused when the event occurred
     * @param keyCode
     *          the physical key that was released. Constants for this value are
     *          defined in this interface with the KEY prefix
     * @param modifiers
     *          the modifier keys pressed at when the event occurred. This value
     *          is a combination of the bits defined by
     *          {@link com.google.gwt.user.client.ui.KeyboardListener#MODIFIER_SHIFT},
     *          {@link com.google.gwt.user.client.ui.KeyboardListener#MODIFIER_CTRL},
     *          and
     *          {@link com.google.gwt.user.client.ui.KeyboardListener#MODIFIER_ALT}
     * @see com.google.gwt.user.client.ui.KeyboardListener#onKeyUp(Widget, char,
     *      int)
     */
    @Override
    public void onKeyUp(Widget sender, char keyCode, int modifiers)
    {
      reinitValueIfNeeded();
    }
  }

  /**
   * the list of registered {@link ChangeListener}.
   */
  private List<ChangeListener> changeListeners = null;

  /**
   * the {@link com.google.gwt.user.client.ui.SuggestOracle} being used.
   */
  private final transient AbstractCustomSuggestOracle oracle;

  /**
   * the event listener to reset the value of this widget.
   */
  private final ResetSuggestBoxEventListener resetSuggestBoxEventListener = new ResetSuggestBoxEventListener();

  /**
   * the {@link SuggestBox} being wrapped.
   */
  private final transient SuggestBox suggestBox;

  /**
   * the current displayed text associated to the current value, or
   * <code>null</code> if no value is defined.
   */
  private String textValue = null;

  /**
   * the current value of the widget.
   */
  private String value = null;



  /**
   * Constructor.
   * 
   * @param oracle
   *          the {@link com.google.gwt.user.client.ui.SuggestOracle} to use.
   */
  public SelectSuggestBox(AbstractCustomSuggestOracle oracle)
  {
    this.oracle = oracle;
    this.suggestBox = new SuggestBox(oracle);
    suggestBox.addEventHandler(new SuggestionHandler()
    {

      public void onSuggestionSelected(SuggestionEvent suggestionEvent)
      {
        String value = null;
        Suggestion suggestion = suggestionEvent.getSelectedSuggestion();
        if (suggestion instanceof CustomSuggestion) {
          value = ((CustomSuggestion) suggestion).getValue();
        }
        else {
          value = suggestion.getReplacementString();
        }
        setValue(value);
      }
    });

    suggestBox.addChangeListener(resetSuggestBoxEventListener);
    suggestBox.addKeyboardListener(resetSuggestBoxEventListener);

    initWidget(suggestBox);
  }



  /**
   * register a new {@link ChangeListener} with this widget.
   * 
   * @param listener
   *          the {@link ChangeListener} to register
   */
  public void addChangeListener(ChangeListener listener)
  {
    if (changeListeners == null) {
      changeListeners = new ArrayList<ChangeListener>();
    }
    changeListeners.add(listener);

  }



  /**
   * getter for the value property.
   * 
   * @return the value
   */
  public String getValue()
  {
    return value;
  }



  /**
   * unregister a new {@link ChangeListener} with this widget.
   * 
   * @param listener
   *          the {@link ChangeListener} to unregister
   */
  public void removeChangeListener(ChangeListener listener)
  {
    if (changeListeners != null) {
      changeListeners.remove(listener);
    }
  }



  /**
   * setter for the value property.
   * 
   * @param value
   *          the value to set
   */
  public void setValue(final String value)
  {
    if (!GwtObjectUtils.nullSafeEquals(this.value, value)) {
      oracle.requestText(value, new HasText()
      {

        public String getText()
        {
          return null;
        }



        public void setText(final String text)
        {
          suggestBox.setText(text);
          DeferredCommand.addCommand(new Command()
          {
            public void execute()
            {
              SelectSuggestBox.this.value = value;
              SelectSuggestBox.this.textValue = text;
              fireChangeEvent();
            }
          });
        }
      });
    }
  }



  /**
   * method to be called when the value of this widget change. It calls all the
   * {@link ChangeListener} registered with this widget.
   */
  private void fireChangeEvent()
  {
    if (changeListeners != null) {
      for(final ChangeListener listener  : changeListeners) {
        listener.onChange(this);
      }
    }
  }



  /**
   * reinit the value of the box if the user changed the text entered and that a
   * value was previously entered.
   */
  private void reinitValueIfNeeded()
  {
    if (value != null && !GwtObjectUtils.nullSafeEquals(textValue, suggestBox.getText())) {
      setValue(null);
    }
  }

}
